package com.chenfengyao.gamereversi.ai

import com.chenfengyao.gamereversi.common.CELL_COUNT
import com.chenfengyao.gamereversi.core.ChessPiece
import kotlin.math.max
import kotlin.math.min

/**
 * Minimax + Alpha-Beta + Iterative Deepening with time budget
 * and a composite evaluation for Reversi.
 */
object AISearch {

    data class Config(
        val maxDepth: Int,
        val timeLimitMs: Long,
        val evalWeights: EvalWeights,
        val randomizeTopK: Int = 1, // for EASY we can randomize among top K
        val useMoveOrdering: Boolean = true,
        val passKeepsDepth: Boolean = false // when true, a pass does not reduce depth (useful for HARD)
    )

    data class EvalWeights(
        val wPosition: Int,
        val wMobility: Int,
        val wCorner: Int,
        val wFrontier: Int,
        val wDiscsEndgame: Int,
        val endgameEmptiesThreshold: Int,
        val wEdgeStable: Int = 0, // edge stability (HARD higher)
        val wPotential: Int = 0,  // potential mobility weight
        val wParity: Int = 0,     // endgame parity weight
        val cxPenC: Int = 0,      // dynamic corner-neighborhood penalties/bonuses
        val cxPenX: Int = 0,
        val cxBonusC: Int = 0,
        val cxBonusX: Int = 0
    )

    private val directions = arrayOf(
        intArrayOf(-1, -1), intArrayOf(-1, 0), intArrayOf(-1, 1),
        intArrayOf(0, -1), intArrayOf(0, 1),
        intArrayOf(1, -1), intArrayOf(1, 0), intArrayOf(1, 1)
    )

    private val posWeights = arrayOf(
        intArrayOf(100, -20, 10, 6, 6, 10, -20, 100),
        intArrayOf(-20, -50, -2, -2, -2, -2, -50, -20),

        intArrayOf(10, -2, 3, 2, 2, 3, -2, 10),
        intArrayOf(6, -2, 2, 0, 0, 2, -2, 6),
        intArrayOf(6, -2, 2, 0, 0, 2, -2, 6),
        intArrayOf(10, -2, 3, 2, 2, 3, -2, 10),
        intArrayOf(-20, -50, -2, -2, -2, -2, -50, -20),
        intArrayOf(100, -20, 10, 6, 6, 10, -20, 100)
    )

    // Transposition Table support (top-level)
    private val zobristBlack: Array<LongArray> = Array(CELL_COUNT) { LongArray(CELL_COUNT) { kotlin.random.Random.nextLong() } }
    private val zobristWhite: Array<LongArray> = Array(CELL_COUNT) { LongArray(CELL_COUNT) { kotlin.random.Random.nextLong() } }
    private val zobristSide: Long = kotlin.random.Random.nextLong()

    private fun computeHash(board: Array<Array<ChessPiece>>, sideToMove: ChessPiece): Long {
        var h = 0L
        for (i in 0 until CELL_COUNT) {
            for (j in 0 until CELL_COUNT) {
                when (board[i][j]) {
                    ChessPiece.BLACK -> h = h xor zobristBlack[i][j]
                    ChessPiece.WHITE -> h = h xor zobristWhite[i][j]
                    else -> {}
                }
            }
        }
        if (sideToMove == ChessPiece.BLACK) h = h xor zobristSide
        return h
    }

    private const val TT_FLAG_EXACT = 0
    private const val TT_FLAG_LOWER = 1
    private const val TT_FLAG_UPPER = 2

    private data class TTEntry(val depth: Int, val score: Int, val flag: Int, val bestMove: Pair<Int, Int>?)

    // Create a new transposition table per search
    private fun newTT(): HashMap<Long, TTEntry> = HashMap(1 shl 15)

    fun defaultConfigFor(difficulty: AIDifficulty): Config = when (difficulty) {
        AIDifficulty.EASY -> Config(
            maxDepth = 1,
            timeLimitMs = 150,
            evalWeights = EvalWeights(
                wPosition = 8, wMobility = 10, wCorner = 25, wFrontier = -6, wDiscsEndgame = 12,
                endgameEmptiesThreshold = 12
            ),
            randomizeTopK = 5,
            useMoveOrdering = false,
            passKeepsDepth = false
        )
        AIDifficulty.MEDIUM -> Config(
            maxDepth = 4,
            timeLimitMs = 400,
            evalWeights = EvalWeights(
                wPosition = 10, wMobility = 18, wCorner = 30, wFrontier = -8, wDiscsEndgame = 16,
                endgameEmptiesThreshold = 12,
                wEdgeStable = 4,
                wPotential = 3,
                wParity = 0,
                cxPenC = 20, cxPenX = 35, cxBonusC = 4, cxBonusX = 7
            ),
            useMoveOrdering = true,
            passKeepsDepth = false
        )
        AIDifficulty.HARD -> Config(
            maxDepth = 6,
            timeLimitMs = 1500,
            evalWeights = EvalWeights(
                wPosition = 10, wMobility = 24, wCorner = 45, wFrontier = -10, wDiscsEndgame = 22,
                endgameEmptiesThreshold = 14,
                wEdgeStable = 12,
                wPotential = 8,
                wParity = 6,

                cxPenC = 40, cxPenX = 70, cxBonusC = 8, cxBonusX = 14
            ),
            useMoveOrdering = true,
            passKeepsDepth = true
        )
    }


    fun findBestMove(
        board: Array<Array<ChessPiece>>,
        player: ChessPiece,
        difficulty: AIDifficulty
    ): Pair<Int, Int> {
        if (player == ChessPiece.NONE) return -1 to -1
        val cfg = defaultConfigFor(difficulty)

        // EASY: choose a random legal move without search to make it clearly weaker
        // Prepare TT per search
        val tt = if (difficulty == AIDifficulty.HARD) newTT() else null

        if (difficulty == AIDifficulty.EASY) {
            val easyLegal = legalMoves(board, player)
            return if (easyLegal.isNotEmpty()) easyLegal.random() else (-1 to -1)
        }

        fun isCorner(x: Int, y: Int): Boolean =
            (x == 0 && y == 0) || (x == 0 && y == CELL_COUNT - 1) || (x == CELL_COUNT - 1 && y == 0) || (x == CELL_COUNT - 1 && y == CELL_COUNT - 1)

        fun canForceCornerNextLocal(board0: Array<Array<ChessPiece>>, me: ChessPiece, mv: Pair<Int, Int>): Boolean {
            val b1 = cloneBoard(board0)
            applyMove(b1, mv.first, mv.second, me)
            val opp = opponent(me)
            val oppMoves = legalMoves(b1, opp)
            if (oppMoves.isEmpty()) return false
            for (om in oppMoves) {
                val b2 = cloneBoard(b1)
                applyMove(b2, om.first, om.second, opp)
                if (legalMoves(b2, me).any { isCorner(it.first, it.second) }) return true
            }
            return false
        }

        // Root-level hard tactics: take corner immediately; else prefer forcing-corner moves
        val legalRoot = legalMoves(board, player)
        if (legalRoot.isEmpty()) return -1 to -1
        val cornerMovesRoot = legalRoot.filter { isCorner(it.first, it.second) }
        if (cornerMovesRoot.isNotEmpty()) {
            var bestCorner = cornerMovesRoot.first()
            var bestCornerScore = Int.MIN_VALUE
            for (m in cornerMovesRoot) {
                val child = cloneBoard(board)
                applyMove(child, m.first, m.second, player)
                val s = evaluate(child, player, cfg.evalWeights)
                if (s > bestCornerScore) { bestCornerScore = s; bestCorner = m }
            }
            return bestCorner
        }
        val forcingMoves = legalRoot.filter { canForceCornerNextLocal(board, player, it) }
        if (forcingMoves.isNotEmpty()) {
            var bestF = forcingMoves.first()
            var bestS = Int.MIN_VALUE
            for (m in forcingMoves) {
                val child = cloneBoard(board)
                applyMove(child, m.first, m.second, player)
                val s = evaluate(child, player, cfg.evalWeights)
                if (s > bestS) { bestS = s; bestF = m }
            }
            return bestF
        }

        val startTime = System.nanoTime()
        val timeLimitNs = cfg.timeLimitMs * 1_000_000

        var bestMove: Pair<Int, Int> = -1 to -1
        var bestScore = Int.MIN_VALUE

        // Iterative deepening
        val legal = legalRoot
        val baseOrderedMoves = if (!cfg.useMoveOrdering) legal.shuffled() else orderMoves(board, player, legal)

        for (depth in 1..cfg.maxDepth) {
            // Principal Variation (PV) move ordering: try previous best first
            val movesThisIter = if (bestMove.first != -1) {
                val pv = bestMove
                val rest = baseOrderedMoves.filterNot { it == pv }
                listOf(pv) + rest
            } else baseOrderedMoves

            var localBest = bestMove
            var localBestScore = Int.MIN_VALUE
            var alpha = Int.MIN_VALUE + 1
            var beta = Int.MAX_VALUE - 1

            for ((x, y) in movesThisIter) {
                if (System.nanoTime() - startTime > timeLimitNs) break
                val child = cloneBoard(board)
                applyMove(child, x, y, player)
                var alphaLocal = -beta
                var betaLocal = -alpha
                val useTT = difficulty == AIDifficulty.HARD
                if (useTT) {
                    val h = computeHash(child, opponent(player))
                    val hit = tt?.get(h)
                    if (hit != null && hit.depth >= depth - 1) {
                        when (hit.flag) {
                            TT_FLAG_EXACT -> {
                                val score = -hit.score
                                if (score > localBestScore) { localBestScore = score; localBest = x to y }
                                alpha = max(alpha, score)
                                if (alpha >= beta) break
                                continue
                            }
                            TT_FLAG_LOWER -> alphaLocal = max(alphaLocal, -hit.score)
                            TT_FLAG_UPPER -> betaLocal = min(betaLocal, -hit.score)
                        }
                    }
                }
                val score = -alphabeta(
                    child,
                    opponent(player),
                    depth - 1,
                    alphaLocal,
                    betaLocal,
                    player, // evaluate from root player's perspective
                    startTime,
                    timeLimitNs,
                    cfg,
                    useTT,
                    tt,
                    ::computeHash
                )
                if (score > localBestScore) {
                    localBestScore = score
                    localBest = x to y
                }
                alpha = max(alpha, score)
                if (alpha >= beta) break
            }

            // If we got at least one evaluation at this depth, adopt it
            if (localBest.first != -1) {
                bestMove = localBest
                bestScore = localBestScore
            }
            if (System.nanoTime() - startTime > timeLimitNs) break
        }

        // Optional: small randomization among topK for EASY
        if (cfg.randomizeTopK > 1) {
            val scored = baseOrderedMoves.map { m ->
                val child = cloneBoard(board)
                applyMove(child, m.first, m.second, player)
                var s = evaluate(child, player, cfg.evalWeights)
                if (!cfg.useMoveOrdering) {
                    // Add small noise to encourage variability for EASY/MEDIUM without ordering
                    s += kotlin.random.Random.nextInt(-3, 4)
                }
                m to s
            }.sortedByDescending { it.second }
            val top = scored.take(max(1, min(cfg.randomizeTopK, scored.size)))
            return if (top.isNotEmpty()) top.random().first else bestMove
        }

        return bestMove
    }

    private fun alphabeta(
        board: Array<Array<ChessPiece>>,
        sideToMove: ChessPiece,
        depth: Int,
        alphaIn: Int,
        betaIn: Int,
        rootPlayer: ChessPiece,
        startTime: Long,
        timeLimitNs: Long,
        cfg: Config,
        useTT: Boolean = false,
        tt: HashMap<Long, TTEntry>? = null,
        computeHash: ((Array<Array<ChessPiece>>, ChessPiece) -> Long)? = null
    ): Int {
        if (System.nanoTime() - startTime > timeLimitNs) {
            return evaluate(board, rootPlayer, cfg.evalWeights)
        }
        val myLegal = legalMoves(board, sideToMove)
        val opp = opponent(sideToMove)
        val oppLegalExists = hasAnyMove(board, opp)

        if (depth == 0 || (!myLegal.any() && !oppLegalExists)) {
            return evaluate(board, rootPlayer, cfg.evalWeights)
        }

        if (!myLegal.any()) {
            // Pass move
            val nextDepth = if (cfg.passKeepsDepth) depth else depth - 1
            return -alphabeta(board, opp, nextDepth, -betaIn, -alphaIn, rootPlayer, startTime, timeLimitNs, cfg, useTT, tt, computeHash)
        }

        var alpha = alphaIn
        var beta = betaIn

        // TT lookup at node
        if (useTT) {
            val h = computeHash?.invoke(board, sideToMove)
            val hit = if (h != null) tt?.get(h) else null
            if (hit != null && hit.depth >= depth) {
                when (hit.flag) {
                    TT_FLAG_EXACT -> return hit.score
                    TT_FLAG_LOWER -> alpha = max(alpha, hit.score)
                    TT_FLAG_UPPER -> beta = min(beta, hit.score)
                }
                if (alpha >= beta) return hit.score
            }
        }

        val ordered = if (!cfg.useMoveOrdering) myLegal.shuffled() else orderMoves(board, sideToMove, myLegal)
        for ((x, y) in ordered) {
            if (System.nanoTime() - startTime > timeLimitNs) break
            val child = cloneBoard(board)
            applyMove(child, x, y, sideToMove)
            val score = -alphabeta(child, opp, depth - 1, -beta, -alpha, rootPlayer, startTime, timeLimitNs, cfg, useTT, tt, computeHash)
            if (score > alpha) {
                alpha = score
            }
            if (alpha >= beta) {
                break
            }
        }

        // Store into TT
        if (useTT) {
            val h = computeHash?.invoke(board, sideToMove)
            if (h != null) {
                val flag = when {
                    alpha <= alphaIn -> TT_FLAG_UPPER
                    alpha >= betaIn -> TT_FLAG_LOWER
                    else -> TT_FLAG_EXACT
                }
                tt?.put(h, TTEntry(depth, alpha, flag, ordered.firstOrNull()))
            }
        }

        return alpha
    }

    private fun evaluate(board: Array<Array<ChessPiece>>, rootPlayer: ChessPiece, w: EvalWeights): Int {
        val opp = opponent(rootPlayer)
        var posScore = 0
        var myDiscs = 0
        var oppDiscs = 0
        var empties = 0
        var myFrontier = 0
        var oppFrontier = 0

        var myCorners = 0
        var oppCorners = 0

        // corner positions
        val corners = arrayOf(0 to 0, 0 to 7, 7 to 0, 7 to 7)

        for (i in 0 until CELL_COUNT) {
            for (j in 0 until CELL_COUNT) {
                val p = board[i][j]
                if (p == ChessPiece.NONE) {
                    empties++
                } else if (p == rootPlayer) {
                    myDiscs++
                    // Avoid double-counting corners via posWeights; corners will be handled by wCorner
                    if (!((i == 0 || i == 7) && (j == 0 || j == 7))) {
                        posScore += posWeights[i][j]
                    }
                    if (isFrontier(board, i, j)) myFrontier++
                } else {
                    oppDiscs++
                    posScore -= posWeights[i][j]
                    if (isFrontier(board, i, j)) oppFrontier++
                }
            }
        }

        for ((cx, cy) in corners) {
            when (board[cx][cy]) {
                rootPlayer -> myCorners++
                opp -> oppCorners++
                else -> {}
            }
        }

        val mobility = legalMovesCount(board, rootPlayer) - legalMovesCount(board, opp)
        // Potential mobility: number of empty squares adjacent to opponent discs minus adjacent to our discs
        val potential = potentialMobility(board, rootPlayer) - potentialMobility(board, opp)

        var score = 0
        score += w.wPosition * posScore
        score += w.wMobility * mobility
        score += w.wCorner * (myCorners - oppCorners)
        score += w.wFrontier * (myFrontier - oppFrontier)
        score += w.wPotential * potential

        if (empties <= w.endgameEmptiesThreshold) {
            score += w.wDiscsEndgame * (myDiscs - oppDiscs)
            // endgame parity (prefer positions with favorable parity)
            score += w.wParity * endgameParity(board, rootPlayer)
        }

        // Dynamic corner-neighborhood adjustment for C/X squares
        score += dynamicCornerNeighborhoodDelta(board, rootPlayer, w)
        // Edge stability (approximate stable discs along edges from owned corners)
        score += w.wEdgeStable * edgeStability(board, rootPlayer)

        return score
    }

    private fun endgameParity(board: Array<Array<ChessPiece>>, rootPlayer: ChessPiece): Int {
        // Simple parity heuristic: prefer even/odd empties so that opponent is forced to move last.
        // Here we use a naive proxy based on empties parity.
        var empties = 0
        for (i in 0 until CELL_COUNT) for (j in 0 until CELL_COUNT) if (board[i][j] == ChessPiece.NONE) empties++
        // Favor odd empties if it's good for root; heuristic sign can be tuned by wParity
        return if (empties % 2 == 1) 1 else -1
    }

    private fun potentialMobility(board: Array<Array<ChessPiece>>, player: ChessPiece): Int {
        val opp = opponent(player)
        var count = 0
        for (i in 0 until CELL_COUNT) for (j in 0 until CELL_COUNT) {
            if (board[i][j] == ChessPiece.NONE) {
                // if adjacent to opponent disc, it's potential mobility for player
                loop@ for (d in directions) {

                    val nx = i + d[0]
                    val ny = j + d[1]
                    if (nx in 0 until CELL_COUNT && ny in 0 until CELL_COUNT && board[nx][ny] == opp) {
                        count++
                        break@loop
                    }
                }
            }
        }
        return count
    }

    // Apply dynamic penalties/bonuses to C/X squares based on corner ownership (weights configurable)
    private fun dynamicCornerNeighborhoodDelta(board: Array<Array<ChessPiece>>, rootPlayer: ChessPiece, w: EvalWeights): Int {
        val opp = opponent(rootPlayer)
        var delta = 0
        val corners = arrayOf(
            Triple(0 to 0, listOf(0 to 1, 1 to 0), 1 to 1),
            Triple(0 to 7, listOf(0 to 6, 1 to 7), 1 to 6),
            Triple(7 to 0, listOf(6 to 0, 7 to 1), 6 to 1),
            Triple(7 to 7, listOf(7 to 6, 6 to 7), 6 to 6)
        )
        for ((corner, cList, xPos) in corners) {
            val (cx, cy) = corner
            val owner = board[cx][cy]
            val cornerOwnedByRoot = owner == rootPlayer
            if (!cornerOwnedByRoot) {
                for ((ax, ay) in cList) {
                    val p = board[ax][ay]
                    if (p == rootPlayer) delta -= w.cxPenC else if (p == opp) delta += w.cxPenC
                }
                val pX = board[xPos.first][xPos.second]
                if (pX == rootPlayer) delta -= w.cxPenX else if (pX == opp) delta += w.cxPenX
            } else {
                for ((ax, ay) in cList) {
                    val p = board[ax][ay]
                    if (p == rootPlayer) delta += w.cxBonusC else if (p == opp) delta -= w.cxBonusC
                }
                val pX = board[xPos.first][xPos.second]
                if (pX == rootPlayer) delta += w.cxBonusX else if (pX == opp) delta -= w.cxBonusX
            }
        }
        return delta
    }

    private fun isCorner(x: Int, y: Int): Boolean =
        (x == 0 && y == 0) || (x == 0 && y == CELL_COUNT - 1) || (x == CELL_COUNT - 1 && y == 0) || (x == CELL_COUNT - 1 && y == CELL_COUNT - 1)

    // Check if playing mv allows us to force a corner next (in two plies)
    private fun canForceCornerNext(board0: Array<Array<ChessPiece>>, me: ChessPiece, mv: Pair<Int, Int>): Boolean {
        val b1 = cloneBoard(board0)
        applyMove(b1, mv.first, mv.second, me)
        val opp = opponent(me)
        val oppMoves = legalMoves(b1, opp)
        if (oppMoves.isEmpty()) return false
        for (om in oppMoves) {
            val b2 = cloneBoard(b1)
            applyMove(b2, om.first, om.second, opp)
            if (legalMoves(b2, me).any { isCorner(it.first, it.second) }) return true
        }
        return false
    }

    // Approximate edge stability score: for each corner owned by root/opponent, count contiguous same-color discs along edges
    private fun edgeStability(board: Array<Array<ChessPiece>>, rootPlayer: ChessPiece): Int {
        val opp = opponent(rootPlayer)
        var myStable = 0
        var oppStable = 0
        // Four edges: top (0,*), bottom (7,*), left (*,0), right (*,7)
        // From each corner, extend along edges while same color and no holes
        fun countFromCorner(cx: Int, cy: Int, dx: Int, dy: Int, who: ChessPiece): Int {
            var cnt = 0
            var x = cx + dx
            var y = cy + dy
            while (x in 0 until CELL_COUNT && y in 0 until CELL_COUNT) {
                val p = board[x][y]
                if (p != who) break
                cnt++
                x += dx
                y += dy
            }
            return cnt
        }
        val corners = arrayOf(0 to 0, 0 to 7, 7 to 0, 7 to 7)
        for ((cx, cy) in corners) {
            val owner = board[cx][cy]
            if (owner == rootPlayer) {
                if (cx == 0 && cy == 0) { myStable += countFromCorner(cx, cy, 1, 0, owner) + countFromCorner(cx, cy, 0, 1, owner) }
                if (cx == 0 && cy == 7) { myStable += countFromCorner(cx, cy, 1, 0, owner) + countFromCorner(cx, cy, 0, -1, owner) }
                if (cx == 7 && cy == 0) { myStable += countFromCorner(cx, cy, -1, 0, owner) + countFromCorner(cx, cy, 0, 1, owner) }
                if (cx == 7 && cy == 7) { myStable += countFromCorner(cx, cy, -1, 0, owner) + countFromCorner(cx, cy, 0, -1, owner) }
            } else if (owner == opp) {
                if (cx == 0 && cy == 0) { oppStable += countFromCorner(cx, cy, 1, 0, owner) + countFromCorner(cx, cy, 0, 1, owner) }
                if (cx == 0 && cy == 7) { oppStable += countFromCorner(cx, cy, 1, 0, owner) + countFromCorner(cx, cy, 0, -1, owner) }
                if (cx == 7 && cy == 0) { oppStable += countFromCorner(cx, cy, -1, 0, owner) + countFromCorner(cx, cy, 0, 1, owner) }
                if (cx == 7 && cy == 7) { oppStable += countFromCorner(cx, cy, -1, 0, owner) + countFromCorner(cx, cy, 0, -1, owner) }
            }
        }
        return myStable - oppStable
    }


    private fun isFrontier(board: Array<Array<ChessPiece>>, x: Int, y: Int): Boolean {
        if (board[x][y] == ChessPiece.NONE) return false
        for (d in directions) {
            val nx = x + d[0]
            val ny = y + d[1]
            if (nx in 0 until CELL_COUNT && ny in 0 until CELL_COUNT && board[nx][ny] == ChessPiece.NONE) return true
        }
        return false
    }

    private fun legalMoves(board: Array<Array<ChessPiece>>, player: ChessPiece): List<Pair<Int, Int>> {
        val list = ArrayList<Pair<Int, Int>>()
        for (i in 0 until CELL_COUNT) {
            for (j in 0 until CELL_COUNT) {
                if (canPlace(board, i, j, player)) list.add(i to j)
            }
        }
        return list
    }

    private fun legalMovesCount(board: Array<Array<ChessPiece>>, player: ChessPiece): Int {
        var c = 0
        for (i in 0 until CELL_COUNT) {
            for (j in 0 until CELL_COUNT) {
                if (canPlace(board, i, j, player)) c++
            }
        }
        return c
    }

    private fun orderMoves(board: Array<Array<ChessPiece>>, player: ChessPiece, moves: List<Pair<Int, Int>>): List<Pair<Int, Int>> {
        val corners = setOf(0 to 0, 0 to 7, 7 to 0, 7 to 7)
        return moves.sortedWith(compareByDescending<Pair<Int, Int>> { it in corners }
            .thenByDescending { (x, y) ->
                val edge = x == 0 || x == 7 || y == 0 || y == 7
                if (edge) 1 else 0
            }
            .thenByDescending { (x, y) -> posWeights[x][y] })
    }

    private fun canPlace(board: Array<Array<ChessPiece>>, x: Int, y: Int, player: ChessPiece): Boolean {
        if (board[x][y] != ChessPiece.NONE) return false
        val opp = opponent(player)
        for (d in directions) {
            var tx = x + d[0]
            var ty = y + d[1]
            var count = 0
            while (tx in 0 until CELL_COUNT && ty in 0 until CELL_COUNT) {
                val p = board[tx][ty]
                if (p == ChessPiece.NONE) break
                if (p == opp) {
                    count++
                } else {
                    if (count > 0) return true
                    break
                }
                tx += d[0]
                ty += d[1]
            }
        }
        return false
    }

    private fun applyMove(board: Array<Array<ChessPiece>>, x: Int, y: Int, player: ChessPiece) {
        val opp = opponent(player)
        for (d in directions) {
            var tx = x + d[0]
            var ty = y + d[1]
            var count = 0
            while (tx in 0 until CELL_COUNT && ty in 0 until CELL_COUNT) {
                val p = board[tx][ty]
                if (p == ChessPiece.NONE) break
                if (p == opp) {
                    count++
                } else {
                    if (count > 0) {
                        var fx = x + d[0]
                        var fy = y + d[1]
                        while (fx != tx || fy != ty) {
                            board[fx][fy] = player
                            fx += d[0]
                            fy += d[1]
                        }
                    }
                    break
                }
                tx += d[0]
                ty += d[1]
            }
        }
        board[x][y] = player
    }

    private fun hasAnyMove(board: Array<Array<ChessPiece>>, player: ChessPiece): Boolean {
        for (i in 0 until CELL_COUNT) {
            for (j in 0 until CELL_COUNT) {
                if (canPlace(board, i, j, player)) return true
            }
        }
        return false
    }

    private fun cloneBoard(board: Array<Array<ChessPiece>>): Array<Array<ChessPiece>> {
        return Array(CELL_COUNT) { i -> Array(CELL_COUNT) { j -> board[i][j] } }
    }

    private fun opponent(p: ChessPiece): ChessPiece = if (p == ChessPiece.BLACK) ChessPiece.WHITE else ChessPiece.BLACK
}

